<!DOCTYPE html>
<html lang="en">
  <head>
    <title>
        Java-framework-Shiro - rulerLwx Blog
      </title>
        <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport"
      content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, minimal-ui">
    <meta name="renderer" content="webkit">
    <meta http-equiv="Cache-Control" content="no-transform" />
    <meta http-equiv="Cache-Control" content="no-siteapp" />
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <meta name="format-detection" content="telephone=no,email=no,adress=no">
    
    <meta name="theme-color" content="#000000" />
    
    <meta http-equiv="window-target" content="_top" />
    
    
    <meta name="description" content="初识 Shiro 1）什么是shiro Apache Shiro 是Java 的一个安全框架。Shiro 可以非常容易的开发出足够好的应用，其 不仅可以用在JavaSE 环境，也可以用" />
    <meta name="generator" content="Hugo 0.73.0 with theme pure" />
    <title>Java-framework-Shiro - rulerLwx Blog</title>
    
    
    <link rel="stylesheet" href="https://rulerLwx.gitee.io/css/style.min.c4bc7071f132c964c2116bca53b392933f377e5ca7b7051ed245187c621a2d3e.css">
    
    <link rel="stylesheet" href="https://cdn.staticfile.org/highlight.js/9.15.10/styles/github.min.css" async>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.4.2/tocbot.css" async>
    <meta property="og:title" content="Java-framework-Shiro" />
<meta property="og:description" content="初识 Shiro 1）什么是shiro Apache Shiro 是Java 的一个安全框架。Shiro 可以非常容易的开发出足够好的应用，其 不仅可以用在JavaSE 环境，也可以用" />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://rulerLwx.gitee.io/2020/07/java-framework-shiro/" />
<meta property="article:published_time" content="2020-07-01T18:58:47+08:00" />
<meta property="article:modified_time" content="2020-07-01T18:58:47+08:00" />
<meta itemprop="name" content="Java-framework-Shiro">
<meta itemprop="description" content="初识 Shiro 1）什么是shiro Apache Shiro 是Java 的一个安全框架。Shiro 可以非常容易的开发出足够好的应用，其 不仅可以用在JavaSE 环境，也可以用">
<meta itemprop="datePublished" content="2020-07-01T18:58:47&#43;08:00" />
<meta itemprop="dateModified" content="2020-07-01T18:58:47&#43;08:00" />
<meta itemprop="wordCount" content="13358">



<meta itemprop="keywords" content="" /><meta name="twitter:card" content="summary"/>
<meta name="twitter:title" content="Java-framework-Shiro"/>
<meta name="twitter:description" content="初识 Shiro 1）什么是shiro Apache Shiro 是Java 的一个安全框架。Shiro 可以非常容易的开发出足够好的应用，其 不仅可以用在JavaSE 环境，也可以用"/>

    <!--[if lte IE 9]>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/classlist/1.1.20170427/classList.min.js"></script>
      <![endif]-->

    <!--[if lt IE 9]>
        <script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
      <![endif]-->
  </head>

  
  

  <body class="main-center theme-black" itemscope itemtype="http://schema.org/WebPage"><header class="header" itemscope itemtype="http://schema.org/WPHeader">
    <div class="slimContent">
      <div class="navbar-header">
        <div class="profile-block text-center">
          <a id="avatar" href="https://gitee.com/rulerLwx" target="_blank">
            <img class="img-circle img-rotate" src="https://rulerLwx.gitee.io/avatar.png" width="200" height="200">
          </a>
          <h2 id="name" class="hidden-xs hidden-sm">rulerLwx</h2>
          <h3 id="title" class="hidden-xs hidden-sm hidden-md">thinking...</h3>
          <small id="location" class="text-muted hidden-xs hidden-sm"><i class="icon icon-map-marker"></i>Guangzhou, China</small>
        </div><div class="search" id="search-form-wrap">
    <form class="search-form sidebar-form">
        <div class="input-group">
            <input type="text" class="search-form-input form-control" placeholder="Search" />
            <span class="input-group-btn">
                <button type="submit" class="search-form-submit btn btn-flat" onclick="return false;"><i
                        class="icon icon-search"></i></button>
            </span>
        </div>
        <div class="ins-search">
            <div class="ins-search-mask"></div>
            <div class="ins-search-container">
                <div class="ins-input-wrapper">
                    <input type="text" class="ins-search-input" placeholder="Type something..."
                        x-webkit-speech />
                    <button type="button" class="close ins-close ins-selectable" data-dismiss="modal"
                        aria-label="Close"><span aria-hidden="true">×</span></button>
                </div>
                <div class="ins-section-wrapper">
                    <div class="ins-section-container"></div>
                </div>
            </div>
        </div>
    </form>
</div>
        <button class="navbar-toggle collapsed" type="button" data-toggle="collapse" data-target="#main-navbar" aria-controls="main-navbar" aria-expanded="false">
          <span class="sr-only">Toggle navigation</span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
        </button>
      </div>
      <nav id="main-navbar" class="collapse navbar-collapse" itemscope itemtype="http://schema.org/SiteNavigationElement" role="navigation">
        <ul class="nav navbar-nav main-nav">
            <li class="menu-item menu-item-home">
                <a href="/">
                    <i class="icon icon-home-fill"></i>
                  <span class="menu-title">Home</span>
                </a>
            </li>
            <li class="menu-item menu-item-archives">
                <a href="/posts/">
                    <i class="icon icon-archives-fill"></i>
                  <span class="menu-title">Archives</span>
                </a>
            </li>
            <li class="menu-item menu-item-categories">
                <a href="/categories/">
                    <i class="icon icon-folder"></i>
                  <span class="menu-title">Categories</span>
                </a>
            </li>
            <li class="menu-item menu-item-tags">
                <a href="/tags/">
                    <i class="icon icon-tags"></i>
                  <span class="menu-title">Tags</span>
                </a>
            </li>
            <li class="menu-item menu-item-about">
                <a href="/about/">
                    <i class="icon icon-cup-fill"></i>
                  <span class="menu-title">About</span>
                </a>
            </li>
        </ul>
      </nav>
    </div>
  </header>

<aside class="sidebar" itemscope itemtype="http://schema.org/WPSideBar">
  <div class="slimContent">
    
      <div class="widget">
    <h3 class="widget-title">Board</h3>
    <div class="widget-body">
        <div id="board">
            <div class="content">enjoy~
            </div>
        </div>
    </div>
</div>

      <div class="widget">
    <h3 class="widget-title"> Categories</h3>
    <div class="widget-body">
        <ul class="category-list">
            <li class="category-list-item"><a href="https://rulerLwx.gitee.io/categories/java-framework/" class="category-list-link">java-framework</a><span class="category-list-count">38</span></li>
            <li class="category-list-item"><a href="https://rulerLwx.gitee.io/categories/java-front-end/" class="category-list-link">java-front-end</a><span class="category-list-count">11</span></li>
            <li class="category-list-item"><a href="https://rulerLwx.gitee.io/categories/java-se/" class="category-list-link">java-se</a><span class="category-list-count">21</span></li>
            <li class="category-list-item"><a href="https://rulerLwx.gitee.io/categories/java-senior/" class="category-list-link">java-senior</a><span class="category-list-count">4</span></li>
            <li class="category-list-item"><a href="https://rulerLwx.gitee.io/categories/linux/" class="category-list-link">linux</a><span class="category-list-count">13</span></li>
            <li class="category-list-item"><a href="https://rulerLwx.gitee.io/categories/tools/" class="category-list-link">tools</a><span class="category-list-count">1</span></li>
            <li class="category-list-item"><a href="https://rulerLwx.gitee.io/categories/%E6%8A%80%E6%9C%AF%E6%9D%82%E7%83%A9/" class="category-list-link">技术杂烩</a><span class="category-list-count">4</span></li>
            <li class="category-list-item"><a href="https://rulerLwx.gitee.io/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/" class="category-list-link">数据库</a><span class="category-list-count">15</span></li>
        </ul>
    </div>
</div>
      <div class="widget">
    <h3 class="widget-title"> Tags</h3>
    <div class="widget-body">
        <ul class="tag-list">
            
            
            <li class="tag-list-item"><a href="https://rulerLwx.gitee.io/tags/jvm/" class="tag-list-link">jvm</a><span
                    class="tag-list-count">1</span></li>
            
            
            <li class="tag-list-item"><a href="https://rulerLwx.gitee.io/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/" class="tag-list-link">多线程</a><span
                    class="tag-list-count">2</span></li>
            
            
            <li class="tag-list-item"><a href="https://rulerLwx.gitee.io/tags/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B/" class="tag-list-link">网络编程</a><span
                    class="tag-list-count">3</span></li>
            
        </ul>

    </div>
</div>
      
<div class="widget">
    <h3 class="widget-title">Recent Posts</h3>
    <div class="widget-body">
        <ul class="recent-post-list list-unstyled no-thumbnail">
            <li>
                <div class="item-inner">
                    <p class="item-title">
                        <a href="https://rulerLwx.gitee.io/2020/07/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E4%B8%80%E6%89%B9%E9%87%8F%E7%A7%BB%E5%8A%A8%E9%87%8D%E5%91%BD%E5%90%8D%E6%96%87%E4%BB%B6/" class="title">自己动手（一）——批量移动、重命名文件</a>
                    </p>
                    <p class="item-date">
                        <time datetime="2020-07-27 17:50:02 &#43;0800 CST" itemprop="datePublished">2020-07-27</time>
                    </p>
                </div>
            </li>
            <li>
                <div class="item-inner">
                    <p class="item-title">
                        <a href="https://rulerLwx.gitee.io/2020/07/%E5%85%B3%E4%BA%8Einteger%E7%9A%84-128~127%E7%BC%93%E5%AD%98/" class="title">关于Integer的 -128~127缓存</a>
                    </p>
                    <p class="item-date">
                        <time datetime="2020-07-11 16:56:21 &#43;0800 CST" itemprop="datePublished">2020-07-11</time>
                    </p>
                </div>
            </li>
            <li>
                <div class="item-inner">
                    <p class="item-title">
                        <a href="https://rulerLwx.gitee.io/2020/07/%E8%B7%A8%E5%9F%9F%E9%97%AE%E9%A2%98/" class="title">跨域问题</a>
                    </p>
                    <p class="item-date">
                        <time datetime="2020-07-08 22:41:12 &#43;0800 CST" itemprop="datePublished">2020-07-08</time>
                    </p>
                </div>
            </li>
            <li>
                <div class="item-inner">
                    <p class="item-title">
                        <a href="https://rulerLwx.gitee.io/2020/07/%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA/" class="title">个人博客搭建</a>
                    </p>
                    <p class="item-date">
                        <time datetime="2020-07-05 18:58:47 &#43;0800 CST" itemprop="datePublished">2020-07-05</time>
                    </p>
                </div>
            </li>
            <li>
                <div class="item-inner">
                    <p class="item-title">
                        <a href="https://rulerLwx.gitee.io/2020/07/centos/" class="title">CentOS-</a>
                    </p>
                    <p class="item-date">
                        <time datetime="2020-07-01 18:58:47 &#43;0800 CST" itemprop="datePublished">2020-07-01</time>
                    </p>
                </div>
            </li>
        </ul>
    </div>
</div>
  </div>
</aside>

    
    
<aside class="sidebar sidebar-toc collapse" id="collapseToc" itemscope itemtype="http://schema.org/WPSideBar">
  <div class="slimContent">
    <h4 class="toc-title">Catalogue</h4>
    <nav id="toc" class="js-toc toc">

    </nav>
  </div>
</aside>
<main class="main" role="main"><div class="content">
  <article id="-" class="article article-type-" itemscope
    itemtype="http://schema.org/BlogPosting">
    
    <div class="article-header">
      <h1 itemprop="name">
  <a
    class="article-title"
    href="/2020/07/java-framework-shiro/"
    >Java-framework-Shiro</a
  >
</h1>

      <div class="article-meta">
        
<span class="article-date">
  <i class="icon icon-calendar-check"></i>&nbsp;
<a href="https://rulerLwx.gitee.io/2020/07/java-framework-shiro/" class="article-date">
  <time datetime="2020-07-01 18:58:47 &#43;0800 CST" itemprop="datePublished">2020-07-01</time>
</a>
</span>
<span class="article-category">
  <i class="icon icon-folder"></i>&nbsp;
  <a class="article-category-link" href="/categories/java-framework/"> Java-framework </a>
</span>

        <span class="post-comment"><i class="icon icon-comment"></i>&nbsp;<a href="/2020/07/java-framework-shiro/#comments"
            class="article-comment-link">Comments</a></span>
		<span class="post-wordcount hidden-xs" itemprop="wordCount">Word Count: 13358words</span>
		<span class="post-readcount hidden-xs" itemprop="timeRequired">Read Count: 27minutes </span>
      </div>
    </div>
    <div class="article-entry marked-body js-toc-content" itemprop="articleBody">
      <h1 id="初识-shiro">初识 Shiro</h1>
<p>1）什么是shiro</p>
<p>Apache Shiro 是Java 的一个安全框架。Shiro 可以非常容易的开发出足够好的应用，其
不仅可以用在JavaSE 环境，也可以用在JavaEE 环境。Shiro 可以帮助我们完成：认证、
授权、加密、会话管理、与Web 集成、缓存等。</p>
<p>2）为什么要学shiro</p>
<p>既然shiro 将安全认证相关的功能抽取出来组成一个框架，使用shiro 就可以非常快速的
完成认证、授权等功能的开发，降低系统成本。</p>
<p>shiro 使用广泛，shiro 可以运行在web 应用，非web 应用，集群分布式应用中越来越多
的用户开始使用shiro。</p>
<p>java 领域中spring security(原名Acegi)也是一个开源的权限管理框架，但是spring security
依赖spring 运行，而shiro 就相对独立，最主要是因为shiro 使用简单、灵活，所以现在越来
越多的用户选择shiro。</p>
<p>3）基本功能</p>
<p><img src="https://gitee.com/rulerLwx/PicGo/raw/master/img/20200708084523.png" alt=""></p>
<p>Authentication：身份认证/登录，验证用户是不是拥有相应的身份；</p>
<p>Authorization：授权，即权限验证，验证某个已认证的用户是否拥有某个权限；即判断用
户是否能做事情，常见的如：验证某个用户是否拥有某个角色。或者细粒度的验证某个用
户对某个资源是否具有某个权限；</p>
<p>Session Manager：会话管理，即用户登录后就是一次会话，在没有退出之前，它的所有信
息都在会话中；会话可以是普通JavaSE 环境的，也可以是如Web 环境的；</p>
<p>Cryptography：加密，保护数据的安全性，如密码加密存储到数据库，而不是明文存储；</p>
<ul>
<li>Web Support：Web 支持，可以非常容易的集成到Web 环境；</li>
<li>Caching：缓存，比如用户登录后，其用户信息、拥有的角色/权限不必每次去查，这样可以
提高效率；</li>
<li>Concurrency：shiro 支持多线程应用的并发验证，即如在一个线程中开启另一个线程，能
把权限自动传播过去；</li>
<li>Testing：提供测试支持；</li>
<li>Run As：允许一个用户假装为另一个用户（如果他们允许）的身份进行访问；</li>
<li>Remember Me：记住我，这个是非常常见的功能，即一次登录后，下次再来的话不用登录
了。</li>
</ul>
<p>Shiro 不会去维护用户、维护权限；这些需要我们自己去设计/提供；然后通过
相应的接口注入给Shiro 即可。</p>
<p>4）Shiro 架构</p>
<p>从外部看：</p>
<p><img src="https://gitee.com/rulerLwx/PicGo/raw/master/img/20200708084556.png" alt=""></p>
<p>从内部看：</p>
<p><img src="https://gitee.com/rulerLwx/PicGo/raw/master/img/20200708085430.png" alt=""></p>
<p>Subject：即主体，用于获取主体信息（principals/credentials），外部应用与subject 进行交互，subject 记录了当前操作用户，将用户的
概念理解为当前操作的主体，可能是一个通过浏览器请求的用户，也可能是一个运行的程序。
Subject 在shiro 中是一个接口，接口中定义了很多认证授相关的方法，外部程序通过
subject 进行认证授，而subject 是通过SecurityManager 安全管理器进行认证授权</p>
<p>SecurityManager：即安全管理器，对全部的subject 进行安全管理，它是shiro 的核心，
负责对所有的subject 进行安全管理。通过SecurityManager 可以完成subject 的认证、授权
等，实质上SecurityManager 是通过Authenticator 进行认证，通过Authorizer 进行授权，通
过SessionManager 进行会话管理等。
SecurityManager 是一个接口，继承了Authenticator, Authorizer, SessionManager 这三个接
口。</p>
<p>Authenticator：即认证器，对用户身份进行认证，Authenticator 是一个接口，shiro 提供
ModularRealmAuthenticator 实现类，通过ModularRealmAuthenticator 基本上可以满足大多数
需求，也可以自定义认证器。</p>
<p>Authorizer：即授权器，用户通过认证器认证通过，在访问功能时需要通过授权器判断用
户是否有此功能的操作权限。</p>
<p>Realm：即领域，相当于datasource 数据源，securityManager 进行安全认证需要通过Realm
获取用户权限数据，比如：如果用户身份数据在数据库那么realm 就需要从数据库获取用户
身份信息。
注意：不要把realm 理解成只是从数据源取数据，在realm 中还有认证授权校验的相关
的代码。</p>
<p>sessionManager：即会话管理，shiro 框架定义了一套会话管理，它不依赖web 容器的session，
所以shiro 可以使用在非web 应用上，也可以将分布式应用的会话集中在一点管理，此特性
可使它实现单点登录。</p>
<p>SessionDAO：即会话dao，是对session 会话操作的一套接口，比如要将session 存储到数据库，
可以通过jdbc 将会话存储到数据库。</p>
<p>CacheManager：即缓存管理，将用户权限数据存储在缓存，这样可以提高性能。</p>
<p>Cryptography：即密码管理，shiro 提供了一套加密/解密的组件，方便开发。比如提供常
用的散列、加/解密等功能。</p>
<h1 id="认证">认证</h1>
<h2 id="入门-ini认证即不连数据库">入门-ini认证（即：不连数据库）</h2>
<p>1）基本概念</p>
<p>Authentication，身份验证
即在应用中谁能证明他就是他本人。一般提供如他们的身份ID 一些标识信息来
表明他就是他本人，如提供身份证，用户名/密码来证明。
在shiro 中，用户需要提供 principals （身份）和 credentials（证明）给shiro，从而应用能
验证用户身份</p>
<p>principals：身份，即主体的标识属性，可以是任何东西，如用户名、邮箱等，唯一即可。
一个主体可以有多个principals，但只有一个Primary principals，一般是用户名/密码/手机号。</p>
<p>credentials：证明/凭证，即只有主体知道的安全值，如密码/数字证书等。</p>
<p>最常见的principals 和credentials 组合就是用户名/密码了。</p>
<p>2）认证流程</p>
<p><img src="https://i.loli.net/2020/07/08/7tlMDSauIT3fcqF.png" alt=""></p>
<p>3）代码实现</p>
<p>依赖</p>
<pre><code class="language-xml">    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.apache.shiro&lt;/groupId&gt;
            &lt;artifactId&gt;shiro-all&lt;/artifactId&gt;
            &lt;version&gt;1.4.1&lt;/version&gt;
            &lt;type&gt;pom&lt;/type&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;commons-logging&lt;/groupId&gt;
            &lt;artifactId&gt;commons-logging&lt;/artifactId&gt;
            &lt;version&gt;1.2&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;log4j&lt;/groupId&gt;
            &lt;artifactId&gt;log4j&lt;/artifactId&gt;
            &lt;version&gt;1.2.17&lt;/version&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;
</code></pre>
<p>resource目录下新建 shiro.ini 文件</p>
<pre><code>[users]
zhangsan=1111
lisi=1111
</code></pre>
<p>认证的简单实现</p>
<pre><code class="language-java">public class AuthenticationDemo {
    public static void main(String[] args) {
        // 构建SecurityManager 工厂，IniSecurityManagerFactory 可以从ini文件中初始化SecurityManager环境
        Factory&lt;SecurityManager&gt; factory = new
                IniSecurityManagerFactory(&quot;classpath:shiro.ini&quot;);
        //通过工厂获得SecurityManager 实例
        SecurityManager securityManager = factory.getInstance();
        //将securityManager 设置到运行环境中
        SecurityUtils.setSecurityManager(securityManager);
        //获取subject 实例
        Subject subject = SecurityUtils.getSubject();
        //创建用户名,密码身份验证Token
        UsernamePasswordToken token = new
                UsernamePasswordToken(&quot;zhangsan&quot;, &quot;1111&quot;);
        try {
            //登录，即身份验证
            subject.login(token);
            if (subject.isAuthenticated()) {
                System.out.println(&quot;用户授权成功&quot;);
            }
        } catch (AuthenticationException e) {
            e.printStackTrace();
            //身份认证失败
            //...
        }
        //退出
        subject.logout();
    }
}
</code></pre>
<p>认证流程：</p>
<p><img src="https://gitee.com/rulerLwx/PicGo/raw/master/img/20200708090100.png" alt=""></p>
<h2 id="自定义-realm">自定义 realm</h2>
<p>自定realm的目的：用户认证的信息不用硬编码，比如写在 ini 文件中。</p>
<p>一般需要实现 AuthorizingRealm</p>
<pre><code class="language-java">public class MyRealm extends AuthorizingRealm {
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        return null;
    }
}
</code></pre>
<p>示例：</p>
<p>1）继承 AuthorizingRealm，重写3个方法</p>
<pre><code class="language-java">public class MyRealm extends AuthorizingRealm {

    //当前realm名字
    @Override
    public String getName() {
        return &quot;MyRealm&quot;;
    }

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //入参token：登录时包装的UsernamePasswordToken

        String username = (String) token.getPrincipal();

        String credential = &quot;123456&quot;;//模拟数据库中获取的密码是123456

        SimpleAuthenticationInfo info =
                new SimpleAuthenticationInfo(username, credential, getName());
        return info;
    }
}
</code></pre>
<p>2）增加配置文件 shiro-realm.ini</p>
<pre><code>#自定义一个realm
myRealm=com.demo.shiro.realm.MyRealm
securityManager.realms=$myRealm
</code></pre>
<p>3）测试</p>
<pre><code class="language-java">    @Test
    public void testMyRealm()
    {
        Factory&lt;SecurityManager&gt; factory =
                new IniSecurityManagerFactory(&quot;classpath:shiro-realm.ini&quot;);
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken token =
                new UsernamePasswordToken(&quot;zhangsan&quot;, &quot;123456&quot;);
        try {
            //6、登录
            subject.login(token);

        } catch (Exception ex) {
            //异常处理
            throw ex;
        }

        System.out.println(&quot;是否登录成功：&quot;+subject.isAuthenticated());

        //登出
        subject.logout();

        System.out.println(&quot;是否登录成功：&quot;+subject.isAuthenticated());
    }
</code></pre>
<h2 id="在realm中对密码加密">在realm中对密码加密</h2>
<p>散列算法：一般用于生成数据的摘要信息，是一种不可逆的算法，一般适合存储密码之类的数据，常见的散列算法如MD5、SHA等。一般进行散列时最好提供一个salt（盐），比如加密密码“admin”，产生的散列值是“21232f297a57a5a743894a0e4a801fc3”，可以到一些md5解密网站很容易的通过散列值得到密码“admin”，即如果直接对密码进行散列相对来说破解更容易，此时我们可以加一些只有系统知道的干扰数据，如用户名和ID（即盐）；这样散列的对象是“密码+用户名+ID”，这样生成的散列值相对来说更难破解。</p>
<p>1）自定义realm</p>
<pre><code class="language-java">public class PasswordRealm extends AuthorizingRealm {
    //当前realm名字
    @Override
    public String getName() {
        return &quot;PasswordRealm&quot;;
    }

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //入参token：登录时包装的UsernamePasswordToken

        String username = (String) token.getPrincipal();

        if (!username.equals(&quot;zhangsan&quot;)) {//模拟用户不存在
            return null;
        }

        String credential = &quot;5cb022bfe7cab9f9f6b043d32e58e617&quot;;//模拟数据库中获取的密码是密文

        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(
                username, credential, ByteSource.Util.bytes(&quot;zhangsan&quot;),getName());//记得加盐值
        return info;
    }
}
</code></pre>
<p>注意：使用 ByteSource.Util.bytes(&ldquo;zhangsan&rdquo;) 加盐值</p>
<p>2）编写 resources/shiro-cryptography.ini</p>
<pre><code>[main]
#定义凭证匹配器
credentialsMatcher= org.apache.shiro.authc.credential.HashedCredentialsMatcher
#散列算法
credentialsMatcher.hashAlgorithmName=md5
#散列次数
credentialsMatcher.hashIterations=5

#将凭证匹配器设置到realm
myRealm= com.demo.shiro.realm.PasswordRealm
myRealm.credentialsMatcher=$credentialsMatcher
securityManager.realms=$myRealm
</code></pre>
<p>3）测试</p>
<pre><code class="language-java">    @Test
    public void testPasswordRealm()
    {
        Factory&lt;SecurityManager&gt; factory =
                new IniSecurityManagerFactory(&quot;classpath:shiro-cryptography.ini&quot;);
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken token =
                new UsernamePasswordToken(&quot;zhangsan&quot;, &quot;123456&quot;);
        try {
            //6、登录
            subject.login(token);

        } catch (Exception ex) {
            //异常处理
            throw ex;
        }

        System.out.println(&quot;是否登录成功：&quot;+subject.isAuthenticated());

        //登出
        subject.logout();

        System.out.println(&quot;是否登录成功：&quot;+subject.isAuthenticated());
    }
</code></pre>
<p>4）附件：测试时对密码加密</p>
<pre><code class="language-java">public class Md5Test {
    @Test
    public void testMd5(){
        String password = &quot;123456&quot;;//明文

        Md5Hash md5Hash = new Md5Hash(password);//md5加密

        md5Hash = new Md5Hash(password, &quot;zhangsan&quot;);//md5 + 盐值

        md5Hash = new Md5Hash(password, &quot;zhangsan&quot;, 5);//md5 + 盐值 + 散列次数

        System.out.println(md5Hash);//5cb022bfe7cab9f9f6b043d32e58e617
    }
}
</code></pre>
<h1 id="授权">授权</h1>
<p>授权模式：RBAC，是基于角色的权限管理。</p>
<h2 id="授权方式">授权方式</h2>
<p>1）编程方式，通过写if/else授权代码块完成</p>
<pre><code class="language-java">Subject subject = SecurityUtils.getSubject();  
if(subject.hasRole(“admin”)) {  
    //有权限  
} else {  
    //无权限  
}  

</code></pre>
<p>2）注解方式，通过在执行的Java方法上放置相应的注解完成</p>
<pre><code class="language-java">@RequiresRoles(&quot;admin&quot;)  
@RequiresPermission(“employee:save”)
public void hello() {  
    //有权限  
} 
</code></pre>
<p>3）jsp标签方式，在JSP页面通过相应的标签完成</p>
<pre><code class="language-jsp">&lt;shiro:hasRole name=&quot;admin&quot;&gt;  
         &lt;!— 有权限 —&gt;  
&lt;/shiro:hasRole&gt; 
</code></pre>
<h2 id="ini授权">ini授权</h2>
<p>1）添加 resources/shiro-</p>
<pre><code>[users]
#用户zhang的密码是123，此用户具有role1和role2两个角色
zhangsan=666,role1,role2
lisi=888,role2

[roles]
#角色role1对资源user拥有create、update权限
role1=user:create,user:update
#角色role2对资源user拥有create、delete权限
role2=user:create,user:delete
#角色role3对资源user拥有create权限
role3=user:create
</code></pre>
<p>在ini文件中用户、角色、权限的配置规则是：“用户名=密码，角色1，角色2&hellip;” “角色=权限1，权限2&hellip;”，首先根据用户名找角色，再根据角色找权限，角色是权限集合。</p>
<p>权限字符串的规则是：“资源标识符：操作：资源实例标识符”，意思是对哪个资源的哪个实例具有什么操作，“:”是资源/操作/实例的分割符，权限字符串也可以使用*通配符。</p>
<p>例子：</p>
<ul>
<li>用户创建权限：user:create，或user:create:*</li>
<li>用户修改实例001的权限：user:update:001</li>
<li>用户实例001的所有权限：user：*：001</li>
</ul>
<p>2）测试 角色</p>
<pre><code class="language-java">    @Test
    public void testHashRoles() {
        //1、登录
        Factory&lt;SecurityManager&gt; factory =
                new IniSecurityManagerFactory(&quot;classpath:shiro-permission.ini&quot;);
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token =
                new UsernamePasswordToken(&quot;zhangsan&quot;, &quot;123456&quot;);
        subject.login(token);

        //2、判断是否拥有角色：方式一
        System.out.println(subject.hasRole(&quot;role1&quot;));
        System.out.println(subject.hasAllRoles(Arrays.asList(&quot;role1&quot;,&quot;role2&quot;,&quot;role3&quot;)));
        System.out.println(Arrays.toString(subject.hasRoles(Arrays.asList(&quot;role1&quot;,&quot;role2&quot;,&quot;role3&quot;))));

        //2、判断是否拥有角色：方式二
        subject.checkRole(&quot;role2&quot;);//如果没有角色，将抛出 UnauthorizedException

        //登出
        //subject.logout();
    }
</code></pre>
<p>3）测试 权限</p>
<pre><code class="language-java">    @Test
    public void testHashPermission() {
        //1、登录
        Factory&lt;SecurityManager&gt; factory =
                new IniSecurityManagerFactory(&quot;classpath:shiro-permission.ini&quot;);
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token =
                new UsernamePasswordToken(&quot;zhangsan&quot;, &quot;123456&quot;);
        subject.login(token);

        //2、是否有权限，方式一
        System.out.println(subject.isPermitted(&quot;user:create&quot;));
        System.out.println(subject.isPermitted(&quot;create&quot;));
        System.out.println(subject.isPermittedAll(&quot;user:create&quot;,&quot;user:update&quot;));

        //2、是否有权限，方式二
        subject.checkPermission(&quot;user:create&quot;);//如果没有权限，将抛出 UnauthorizedException
        
        //登出
        //subject.logout();
    }
</code></pre>
<h2 id="自定义-realm-1">自定义 realm</h2>
<p>1）自定义realm</p>
<pre><code class="language-java">public class PermissionRealm extends AuthorizingRealm {
    //当前realm名字
    @Override
    public String getName() {
        return &quot;PermissionRealm&quot;;
    }

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = (String) principals.getPrimaryPrincipal();//SimpleAuthenticationInfo的第一个参数：username

        //模拟从数据库中查询出来的数据
        ArrayList&lt;String&gt; roles = new ArrayList&lt;&gt;();//从数据库中查到的角色集合
        ArrayList&lt;String&gt; permissions = new ArrayList&lt;&gt;();//从数据库中查到的权限集合
        roles.add(&quot;role1&quot;);
        permissions.add(&quot;user:delete&quot;);

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addRoles(roles);
        info.addStringPermissions(permissions);

        return info;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //入参token：登录时包装的UsernamePasswordToken

        String username = (String) token.getPrincipal();

        if (!username.equals(&quot;zhangsan&quot;)) {//模拟用户不存在
            return null;
        }

        String credential = &quot;123456&quot;;

        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(
                username, credential,getName());
        return info;
    }
}
</code></pre>
<p>2）配置resources/shiro-permission-realm.ini</p>
<pre><code>[main]
#声明一个realm
myReal=com.demo.shiro.realm.PermissionRealm
#指定securityManager的realms实现
securityManager.realms=$myReal
</code></pre>
<p>3）</p>
<pre><code class="language-java">    @Test
    public void testRolePermissionByRealm() {
        //1、登录
        Factory&lt;SecurityManager&gt; factory =
                new IniSecurityManagerFactory(&quot;classpath:shiro-permission-realm.ini&quot;);
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token =
                new UsernamePasswordToken(&quot;zhangsan&quot;, &quot;123456&quot;);
        subject.login(token);

        //是否拥有角色
        System.out.println(subject.hasRole(&quot;role1&quot;));

        //是否有权限
        System.out.println(subject.isPermitted(&quot;user:create&quot;));

        //登出
        //subject.logout();
    }
</code></pre>
<h2 id="授权流程分析">授权流程分析</h2>
<p>跟认证类似</p>
<p><img src="https://gitee.com/rulerLwx/PicGo/raw/master/img/20200708090153.png" alt=""></p>
<ol>
<li>首先调用Subject.isPermitted*/hasRole*接口，其会委托给SecurityManager，而SecurityManager接着会委托给Authorizer</li>
<li>Authorizer是真正的授权者，如果我们调用如isPermitted(“user:view”)，其首先会通过PermissionResolver把字符串转换成相应的Permission实例；</li>
<li>在进行授权之前，其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限；</li>
<li>Authorizer会判断Realm的角色/权限是否和传入的匹配，如果有多个Realm，会委托给ModularRealmAuthorizer进行循环判断，如果匹配如isPermitted*/hasRole*会返回true，否则返回false表示授权失败。</li>
</ol>
<h1 id="servlet--shiro">Servlet + Shiro</h1>
<p>Shiro与Web集成，主要是通过配置一个ShiroFilter拦截所有URL，其中ShiroFilter类似于如Strut2/SpringMVC这种web框架的前端控制器，是所有请求入口点，负责根据配置（如ini配置文件），判断请求进入URL是否需要登录/权限等工作。</p>
<p>1）导入依赖</p>
<pre><code class="language-xml">&lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;commons-logging&lt;/groupId&gt;
            &lt;artifactId&gt;commons-logging&lt;/artifactId&gt;
            &lt;version&gt;1.1.3&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;!-- shiro核心的依赖 --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.apache.shiro&lt;/groupId&gt;
            &lt;artifactId&gt;shiro-core&lt;/artifactId&gt;
            &lt;version&gt;${shiro.previousVersion}&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;!-- shiro对web支持的依赖 --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.apache.shiro&lt;/groupId&gt;
            &lt;artifactId&gt;shiro-web&lt;/artifactId&gt;
            &lt;version&gt;${shiro.previousVersion}&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;!-- serlvet-api --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;javax.servlet&lt;/groupId&gt;
            &lt;artifactId&gt;javax.servlet-api&lt;/artifactId&gt;
            &lt;version&gt;3.0.1&lt;/version&gt;
            &lt;scope&gt;provided&lt;/scope&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;
</code></pre>
<p>2）在web.xml中配置 ShiroFilter</p>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;web-app xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
         xmlns=&quot;http://java.sun.com/xml/ns/javaee&quot;
         xmlns:web=&quot;http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd&quot;
         xsi:schemaLocation=&quot;http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd&quot; id=&quot;WebApp_ID&quot; version=&quot;3.0&quot;&gt;

    &lt;!--初始化securityManager对象所需要的环境配置--&gt;
    &lt;context-param&gt;
        &lt;param-name&gt;shiroEnvironmentClass&lt;/param-name&gt;
        &lt;param-value&gt;org.apache.shiro.web.env.IniWebEnvironment&lt;/param-value&gt;
    &lt;/context-param&gt;
    &lt;context-param&gt;
        &lt;param-name&gt;shiroConfigLocations&lt;/param-name&gt;
        &lt;param-value&gt;classpath:shiro.ini&lt;/param-value&gt;
    &lt;/context-param&gt;
    &lt;!--
    	从Shiro 1.2开始引入了Environment/WebEnvironment的概念，即由它们的实现提供相应的SecurityManager及其相应的依赖。
    	ShiroFilter会自动找到Environment然后获取相应的依赖。
    	底层:返回反射创建shiroEnvironmentClass对象,调用其init方法.
    		shiroEnvironmentClass中的init方法创建SecurityManager实例并绑定到当前运行环境
     --&gt;
    &lt;listener&gt;
        &lt;listener-class&gt;org.apache.shiro.web.env.EnvironmentLoaderListener&lt;/listener-class&gt;
    &lt;/listener&gt;


    &lt;filter&gt;
        &lt;filter-name&gt;shiroFilter&lt;/filter-name&gt;
        &lt;filter-class&gt;org.apache.shiro.web.servlet.ShiroFilter&lt;/filter-class&gt;
    &lt;/filter&gt;
    &lt;filter-mapping&gt;
        &lt;filter-name&gt;shiroFilter&lt;/filter-name&gt;
        &lt;!-- 拦截所有的请求 --&gt;
        &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
    &lt;/filter-mapping&gt;
&lt;/web-app&gt;
</code></pre>
<p>3）编写 resources/shiro.ini</p>
<pre><code>[main]
#默认是/login.jsp
authc.loginUrl=/login
#用户无需要的角色时跳转的页面
roles.unauthorizedUrl=/nopermission.jsp
#用户无需要的权限时跳转的页面
perms.unauthorizedUrl=/nopermission.jsp
#登出之后重定向的页面
logout.redirectUrl=/login
[users]
admin=666,admin
zhangsan=666,deptMgr
[roles]
admin=employee:*,department:*
deptMgr=department:view
[urls]
#静态资源可以匿名访问
/static/**=anon
#访问员工列表需要身份认证及需要拥有admin角色
/employee=authc,roles[admin]
#访问部门列表需要身份认证及需要拥有department:view的权限
/department=authc,perms[&quot;department:view&quot;]
#当请求loginOut,会被logout捕获并清除session
/loginOut=logout
#所有的请求都需要身份认证
/**=authc
</code></pre>
<table>
<thead>
<tr>
<th>过滤器简称</th>
<th>对应的java类</th>
</tr>
</thead>
<tbody>
<tr>
<td>anon</td>
<td>org.apache.shiro.web.filter.authc.AnonymousFilter</td>
</tr>
<tr>
<td>authc</td>
<td>org.apache.shiro.web.filter.authc.FormAuthenticationFilter</td>
</tr>
<tr>
<td>authcBasic</td>
<td>org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter</td>
</tr>
<tr>
<td>roles</td>
<td>org.apache.shiro.web.filter.authz.RolesAuthorizationFilter</td>
</tr>
<tr>
<td>perms</td>
<td>org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter</td>
</tr>
<tr>
<td>user</td>
<td>org.apache.shiro.web.filter.authc.UserFilter</td>
</tr>
<tr>
<td>logout</td>
<td>org.apache.shiro.web.filter.authc.LogoutFilter</td>
</tr>
<tr>
<td>port</td>
<td>org.apache.shiro.web.filter.authz.PortFilter</td>
</tr>
<tr>
<td>rest</td>
<td>org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter</td>
</tr>
<tr>
<td>ssl</td>
<td>org.apache.shiro.web.filter.authz.SslFilter</td>
</tr>
</tbody>
</table>
<ul>
<li>anon：匿名拦截器，即不需要登录即可访问；一般用于静态资源过滤；示例“/static/**=anon”</li>
<li>authc：表示需要认证(登录)才能使用;示例“/**=authc”主要属性：
<ul>
<li>usernameParam：表单提交的用户名参数名（ username）；</li>
<li>passwordParam：表单提交的密码参数名（password）；</li>
<li>rememberMeParam：表单提交的密码参数名（rememberMe）；</li>
<li>loginUrl：登录页面地址（/login.jsp）；</li>
<li>successUrl：登录成功后的默认重定向地址；</li>
<li>failureKeyAttribute：登录失败后错误信息存储key（shiroLoginFailure）；</li>
</ul>
</li>
<li>authcBasic：Basic HTTP身份验证拦截器，主要属性：
<ul>
<li>applicationName：弹出登录框显示的信息（application）；</li>
</ul>
</li>
<li>roles：角色授权拦截器，验证用户是否拥有资源角色；示例“/admin/**=roles[admin]”</li>
<li>perms：权限授权拦截器，验证用户是否拥有资源权限；示例“/user/create=perms[&ldquo;user:create&rdquo;]”</li>
<li>user：用户拦截器，用户已经身份验证/记住我登录的都可；示例“/index=user”</li>
<li>logout：退出拦截器，主要属性：
<ul>
<li>redirectUrl：退出成功后重定向的地址（/）;示例“/logout=logout”</li>
</ul>
</li>
<li>port：端口拦截器，主要属性：
<ul>
<li>port（80）：可以通过的端口；示例“/test= port[80]”，如果用户访问该页面是非80，将自动将请求端口改为80并重定向到该80端口，其他路径/参数等都一样</li>
</ul>
</li>
<li>rest：rest风格拦截器，自动根据请求方法构建权限字符串（GET=read, POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create）构建权限字符串；
示例“/users=rest[user]”，会自动拼出“user:read,user:create,user:update,user:delete”权限字符串进行权限匹配（所有都得匹配，isPermittedAll）；</li>
<li>ssl：SSL拦截器，只有请求协议是https才能通过；否则自动跳转会https端口（443）；其他和port拦截器一样；</li>
</ul>
<p>注：</p>
<ul>
<li>anon，authcBasic，auchc，user是认证过滤器，</li>
<li>perms，roles，ssl，rest，port是授权过滤器</li>
</ul>
<p>参考：org.apache.shiro.web.filter.mgt.DefaultFilter 枚举类</p>
<hr>
<p><img src="https://gitee.com/rulerLwx/PicGo/raw/master/img/20200708090515.png" alt=""></p>
<hr>
<p>这些默认的拦截器会自动注册，可以直接在ini配置文件中通过“拦截器名.属性”设置其属性：</p>
<pre><code>#如果身份验证没有通过,就跳转到loginUrl指定的页面
authc.loginUrl=/login  
#如果用户没有角色,就跳转到unauthorizedUrl指定的页面
roles.unauthorizedUrl=/nopermission.jsp 
#如果用户没有权限,就跳转到unauthorizedUrl指定的页面
perms.unauthorizedUrl=/nopermission.jsp
</code></pre>
<p>另外如果某个拦截器不想使用了可以直接通过如下配置直接禁用：
perms.enabled=false</p>
<hr>
<p>authc登录拦截器工作原理</p>
<p>authc拦截器有2个作用：</p>
<p>1）登录认证</p>
<p>请求进来时，拦截并判断当前用户是否登录了，如果已经登录了放行， 如果没有登录，跳转到authc.loginUrl属性配置的路径，注意：默认是/login.jsp</p>
<p>2）执行登录认证</p>
<p>请求进来时，如果<strong>请求的路径为authc.loginUrl属性配置的路径</strong>（没配置，默认是/login.jsp）时，如果当前用户没有登录，authc这个拦截器会尝试获取请求中的账号跟密码值，然后比对ini配置文件或者realm中的用户列表，如果比对正确，直接执行登录操作，反之，抛异常，跳转到authc.loginUrl指定的路径。</p>
<p>注意：请求中账号与密码必须固定为username 跟password， 如果需要改动必须额外指定，authc.usernameParam=xxx   authc.passwordParam=xxxx</p>
<p>authc登录成功之后处理逻辑：</p>
<p><img src="https://gitee.com/rulerLwx/PicGo/raw/master/img/20200708090541.png" alt=""></p>
<p>authc登录失败之后处理逻辑：</p>
<pre><code class="language-java">@Controller
public class LoginController {

    @RequestMapping(&quot;/login&quot;)
    public String login(Model model, HttpServletRequest req) throws  Exception{
        //如果登陆失败从request中获取认证异常信息，shiroLoginFailure就是shiro异常类的全限定名
        String exceptionClassName = (String) req.getAttribute(&quot;shiroLoginFailure&quot;);
        //根据shiro返回的异常类路径判断，抛出指定异常信息
        if(exceptionClassName!=null){
            if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
                //最终会抛给异常处理器

                model.addAttribute(&quot;errorMsg&quot;, &quot;账号不存在&quot;);
            } else if (IncorrectCredentialsException.class.getName().equals(
                    exceptionClassName)) {
                model.addAttribute(&quot;errorMsg&quot;, &quot;用户名/密码错误&quot;);
            } else {
                //最终在异常处理器生成未知错误.
                model.addAttribute(&quot;errorMsg&quot;, &quot;其他异常信息&quot;);
            }
        }
        //此方法不处理登陆成功（认证成功），shiro认证成功会自动跳转到上一个请求路径
        //登陆失败还到login页面
        return &quot;forward:/login.jsp&quot;;
    }
}
</code></pre>
<h1 id="spring-mvc--shiro">Spring MVC + Shiro</h1>
<h2 id="整合shiro">整合Shiro</h2>
<p>参考官方示例：https://github.com/apache/shiro/tree/master/samples/spring-mvc</p>
<p>依赖（从官方示例抄的）：</p>
<pre><code class="language-xml">    &lt;dependencies&gt;
        &lt;!--&lt;dependency&gt;--&gt;
            &lt;!--&lt;groupId&gt;org.apache.shiro.samples&lt;/groupId&gt;--&gt;
            &lt;!--&lt;artifactId&gt;samples-spring-client&lt;/artifactId&gt;--&gt;
        &lt;!--&lt;/dependency&gt;--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.apache.shiro&lt;/groupId&gt;
            &lt;artifactId&gt;shiro-core&lt;/artifactId&gt;
            &lt;version&gt;${shiro.previousVersion}&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.apache.shiro&lt;/groupId&gt;
            &lt;artifactId&gt;shiro-ehcache&lt;/artifactId&gt;
            &lt;version&gt;${shiro.previousVersion}&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;net.sf.ehcache&lt;/groupId&gt;
            &lt;artifactId&gt;ehcache-core&lt;/artifactId&gt;
            &lt;version&gt;${ehcache.version}&lt;/version&gt;
            &lt;optional&gt;false&lt;/optional&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.apache.shiro&lt;/groupId&gt;
            &lt;artifactId&gt;shiro-spring&lt;/artifactId&gt;
            &lt;version&gt;${shiro.previousVersion}&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.apache.shiro&lt;/groupId&gt;
            &lt;artifactId&gt;shiro-web&lt;/artifactId&gt;
            &lt;version&gt;${shiro.previousVersion}&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;javax.servlet&lt;/groupId&gt;
            &lt;artifactId&gt;javax.servlet-api&lt;/artifactId&gt;
            &lt;version&gt;3.1.0&lt;/version&gt;
            &lt;scope&gt;provided&lt;/scope&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
            &lt;artifactId&gt;slf4j-log4j12&lt;/artifactId&gt;
            &lt;version&gt;${slf4j.version}&lt;/version&gt;
            &lt;scope&gt;runtime&lt;/scope&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
            &lt;artifactId&gt;jcl-over-slf4j&lt;/artifactId&gt;
            &lt;version&gt;${slf4j.version}&lt;/version&gt;
            &lt;scope&gt;runtime&lt;/scope&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;log4j&lt;/groupId&gt;
            &lt;artifactId&gt;log4j&lt;/artifactId&gt;
            &lt;version&gt;${log4j.version}&lt;/version&gt;
            &lt;scope&gt;runtime&lt;/scope&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework&lt;/groupId&gt;
            &lt;artifactId&gt;spring-context&lt;/artifactId&gt;
            &lt;version&gt;${spring.version}&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework&lt;/groupId&gt;
            &lt;artifactId&gt;spring-jdbc&lt;/artifactId&gt;
            &lt;version&gt;${spring.version}&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework&lt;/groupId&gt;
            &lt;artifactId&gt;spring-webmvc&lt;/artifactId&gt;
            &lt;version&gt;${spring.version}&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;hsqldb&lt;/groupId&gt;
            &lt;artifactId&gt;hsqldb&lt;/artifactId&gt;
            &lt;version&gt;${hsqldb.version}&lt;/version&gt;
            &lt;scope&gt;runtime&lt;/scope&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;javax.servlet&lt;/groupId&gt;
            &lt;artifactId&gt;jstl&lt;/artifactId&gt;
            &lt;version&gt;1.2&lt;/version&gt;
            &lt;scope&gt;runtime&lt;/scope&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;
</code></pre>
<p>整合 Shiro 之前，先搭建 Spring/SpringMVC 环境，此步省略</p>
<p>在此基础上开始整合 shiro</p>
<p>1）在 web.xml 添加 shiro 过滤器</p>
<pre><code class="language-xml">    &lt;!--
    DelegatingFilterProxy 实际上是 Filter 的一个代理对象. 默认情况下, Spring 会到 IOC 容器中查找和 
	&lt;filter-name&gt; 对应的 filter bean. 也可以通过 targetBeanName 的初始化参数来配置 filter bean 的 id.
    --&gt;
    &lt;filter&gt;
        &lt;filter-name&gt;shiroFilter&lt;/filter-name&gt;
        &lt;filter-class&gt;org.springframework.web.filter.DelegatingFilterProxy&lt;/filter-class&gt;
        &lt;init-param&gt;
            &lt;param-name&gt;targetFilterLifecycle&lt;/param-name&gt;
            &lt;param-value&gt;true&lt;/param-value&gt;
        &lt;/init-param&gt;
    &lt;/filter&gt;
    &lt;filter-mapping&gt;
        &lt;filter-name&gt;shiroFilter&lt;/filter-name&gt;
        &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
    &lt;/filter-mapping&gt;
</code></pre>
<p>2）配置 applicationContext.xml</p>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
       xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
       xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd&quot;&gt;
    &lt;!--1. 配置 SecurityManager!--&gt;
    &lt;bean id=&quot;securityManager&quot; class=&quot;org.apache.shiro.web.mgt.DefaultWebSecurityManager&quot;&gt;
        &lt;property name=&quot;cacheManager&quot; ref=&quot;cacheManager&quot;/&gt;
        &lt;property name=&quot;authenticator&quot; ref=&quot;authenticator&quot;&gt;&lt;/property&gt;

        &lt;property name=&quot;realms&quot;&gt;
            &lt;list&gt;
                &lt;ref bean=&quot;jdbcRealm&quot;/&gt;
                &lt;ref bean=&quot;secondRealm&quot;/&gt;
            &lt;/list&gt;
        &lt;/property&gt;

        &lt;!--&lt;property name=&quot;rememberMeManager.cookie.maxAge&quot; value=&quot;10&quot;&gt;&lt;/property&gt;--&gt;
    &lt;/bean&gt;

    &lt;!--2. 配置 CacheManager.--&gt;
    &lt;!--2.1 需要加入 ehcache 的 jar 包及配置文件.--&gt;
    &lt;bean id=&quot;cacheManager&quot; class=&quot;org.apache.shiro.cache.ehcache.EhCacheManager&quot;&gt;
        &lt;property name=&quot;cacheManagerConfigFile&quot; value=&quot;classpath:ehcache.xml&quot;/&gt;
    &lt;/bean&gt;

    &lt;bean id=&quot;authenticator&quot;
          class=&quot;org.apache.shiro.authc.pam.ModularRealmAuthenticator&quot;&gt;
        &lt;property name=&quot;authenticationStrategy&quot;&gt;
            &lt;bean class=&quot;org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy&quot;&gt;&lt;/bean&gt;
        &lt;/property&gt;
    &lt;/bean&gt;

    	&lt;!--3. 配置 Realm--&gt;
    	&lt;!--3.1 直接配置实现了 org.apache.shiro.realm.Realm 接口的 bean--&gt;
    &lt;bean id=&quot;jdbcRealm&quot; class=&quot;com.demo.shiro.realm.ShiroRealm&quot;&gt;
        &lt;property name=&quot;credentialsMatcher&quot;&gt;
            &lt;bean class=&quot;org.apache.shiro.authc.credential.HashedCredentialsMatcher&quot;&gt;
                &lt;property name=&quot;hashAlgorithmName&quot; value=&quot;MD5&quot;&gt;&lt;/property&gt;
                &lt;property name=&quot;hashIterations&quot; value=&quot;1024&quot;&gt;&lt;/property&gt;
            &lt;/bean&gt;
        &lt;/property&gt;
    &lt;/bean&gt;

    &lt;bean id=&quot;secondRealm&quot; class=&quot;com.demo.shiro.realm.SecondRealm&quot;&gt;
        &lt;property name=&quot;credentialsMatcher&quot;&gt;
            &lt;bean class=&quot;org.apache.shiro.authc.credential.HashedCredentialsMatcher&quot;&gt;
                &lt;property name=&quot;hashAlgorithmName&quot; value=&quot;SHA1&quot;&gt;&lt;/property&gt;
                &lt;property name=&quot;hashIterations&quot; value=&quot;1024&quot;&gt;&lt;/property&gt;
            &lt;/bean&gt;
        &lt;/property&gt;
    &lt;/bean&gt;

    &lt;!--4. 配置 LifecycleBeanPostProcessor. 可以自定的来调用配置在 Spring IOC 容器中 shiro bean 的生命周期方法.--&gt;
    &lt;bean id=&quot;lifecycleBeanPostProcessor&quot; class=&quot;org.apache.shiro.spring.LifecycleBeanPostProcessor&quot;/&gt;

    &lt;!--5. 启用 IOC 容器中使用 shiro 的注解. 但必须在配置了 LifecycleBeanPostProcessor 之后才可以使用.--&gt;
    &lt;bean class=&quot;org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator&quot;
          depends-on=&quot;lifecycleBeanPostProcessor&quot;/&gt;
    &lt;bean class=&quot;org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor&quot;&gt;
        &lt;property name=&quot;securityManager&quot; ref=&quot;securityManager&quot;/&gt;
    &lt;/bean&gt;

    &lt;!--6. 配置 ShiroFilter.--&gt;
    &lt;!--
    
    6.1 id 必须和 web.xml 文件中配置的 DelegatingFilterProxy 的 &lt;filter-name&gt; 一致.
    若不一致, 则会抛出: NoSuchBeanDefinitionException. 
    因为 Shiro 会来 IOC 容器中查找和 &lt;filter-name&gt; 名字对应的 filter bean.
    
    --&gt;
    &lt;bean id=&quot;shiroFilter&quot; class=&quot;org.apache.shiro.spring.web.ShiroFilterFactoryBean&quot;&gt;
        &lt;property name=&quot;securityManager&quot; ref=&quot;securityManager&quot;/&gt;
        &lt;property name=&quot;loginUrl&quot; value=&quot;/login.jsp&quot;/&gt;
        &lt;property name=&quot;successUrl&quot; value=&quot;/list.jsp&quot;/&gt;
        &lt;property name=&quot;unauthorizedUrl&quot; value=&quot;/unauthorized.jsp&quot;/&gt;

        &lt;property name=&quot;filterChainDefinitionMap&quot; ref=&quot;filterChainDefinitionMap&quot;&gt;&lt;/property&gt;

        &lt;!--
        	配置哪些页面需要受保护.
        	以及访问这些页面需要的权限.
        	1). anon 可以被匿名访问
        	2). authc 必须认证(即登录)后才可能访问的页面.
        	3). logout 登出.
        	4). roles 角色过滤器
        --&gt;
        &lt;!--
        &lt;property name=&quot;filterChainDefinitions&quot;&gt;
            &lt;value&gt;
                /login.jsp = anon
                /shiro/login = anon
                /shiro/logout = logout

                /user.jsp = roles[user]
                /admin.jsp = roles[admin]

                # everything else requires authentication:
                /** = authc
            &lt;/value&gt;
        &lt;/property&gt;
        --&gt;
    &lt;/bean&gt;

    &lt;!-- 配置一个 bean, 该 bean 实际上是一个 Map. 通过实例工厂方法的方式 --&gt;
    &lt;bean id=&quot;filterChainDefinitionMap&quot;
          factory-bean=&quot;filterChainDefinitionMapBuilder&quot; factory-method=&quot;buildFilterChainDefinitionMap&quot;&gt;&lt;/bean&gt;

    &lt;bean id=&quot;filterChainDefinitionMapBuilder&quot;
          class=&quot;com.demo.shiro.factory.FilterChainDefinitionMapBuilder&quot;&gt;&lt;/bean&gt;

    &lt;bean id=&quot;shiroService&quot;
          class=&quot;com.demo.shiro.services.ShiroService&quot;&gt;&lt;/bean&gt;
&lt;/beans&gt;
</code></pre>
<p>注意点：<!-- raw HTML omitted -->的名称（id）跟web.xml中的<!-- raw HTML omitted -->要一致。</p>
<h2 id="多-realm-和-认证策略">多 realm 和 认证策略</h2>
<p>为什么要多个 realm ？</p>
<p>AuthenticationStrategy接口的默认实现：</p>
<ul>
<li>FirstSuccessfulStrategy：只要有一个Realm 验证成功即可，只返回第一个Realm 身份验证成功的认证信息，其他的忽略；</li>
<li>AtLeastOneSuccessfulStrategy：只要有一个Realm验证成功即可，和FirstSuccessfulStrategy不同，将返回所有Realm身份验证成功的认证信息；</li>
<li>AllSuccessfulStrategy：所有Realm验证成功才算成功，且返回所有Realm身份验证成功的认证信息，如果有一个失败就失败了。</li>
</ul>
<p>ModularRealmAuthenticator默认是AtLeastOneSuccessfulStrategy策略</p>
<p>参考：</p>
<pre><code class="language-xml">    &lt;!--1. 配置 SecurityManager!--&gt;
    &lt;bean id=&quot;securityManager&quot; class=&quot;org.apache.shiro.web.mgt.DefaultWebSecurityManager&quot;&gt;
        &lt;property name=&quot;cacheManager&quot; ref=&quot;cacheManager&quot;/&gt;
        &lt;property name=&quot;authenticator&quot; ref=&quot;authenticator&quot;&gt;&lt;/property&gt;

        &lt;property name=&quot;realms&quot;&gt;
            &lt;list&gt;
                &lt;ref bean=&quot;jdbcRealm&quot;/&gt;
                &lt;ref bean=&quot;secondRealm&quot;/&gt;
            &lt;/list&gt;
        &lt;/property&gt;
    &lt;/bean&gt;

    &lt;!--认证策略--&gt;
    &lt;bean id=&quot;authenticator&quot;
          class=&quot;org.apache.shiro.authc.pam.ModularRealmAuthenticator&quot;&gt;
        &lt;property name=&quot;authenticationStrategy&quot;&gt;
            &lt;bean class=&quot;org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy&quot;&gt;&lt;/bean&gt;
        &lt;/property&gt;
    &lt;/bean&gt;
    
    &lt;bean id=&quot;jdbcRealm&quot; class=&quot;com.demo.shiro.realm.ShiroRealm&quot;&gt;
        &lt;property name=&quot;credentialsMatcher&quot;&gt;
            &lt;bean class=&quot;org.apache.shiro.authc.credential.HashedCredentialsMatcher&quot;&gt;
                &lt;property name=&quot;hashAlgorithmName&quot; value=&quot;MD5&quot;&gt;&lt;/property&gt;
                &lt;property name=&quot;hashIterations&quot; value=&quot;1024&quot;&gt;&lt;/property&gt;
            &lt;/bean&gt;
        &lt;/property&gt;
    &lt;/bean&gt;

    &lt;bean id=&quot;secondRealm&quot; class=&quot;com.demo.shiro.realm.SecondRealm&quot;&gt;
        &lt;property name=&quot;credentialsMatcher&quot;&gt;
            &lt;bean class=&quot;org.apache.shiro.authc.credential.HashedCredentialsMatcher&quot;&gt;
                &lt;property name=&quot;hashAlgorithmName&quot; value=&quot;SHA1&quot;&gt;&lt;/property&gt;
                &lt;property name=&quot;hashIterations&quot; value=&quot;1024&quot;&gt;&lt;/property&gt;
            &lt;/bean&gt;
        &lt;/property&gt;
    &lt;/bean&gt;
</code></pre>
<p>realms 属性建议配置给 securityManager，因为授权时用到。</p>
<h2 id="权限注解">权限注解</h2>
<ul>
<li>@RequiresAuthentication：表示当前Subject已经通过login 进行了身份验证；即Subject. isAuthenticated() 返回true</li>
<li>@RequiresUser：表示当前Subject 已经身份验证或者通过记住我登录的。</li>
<li>@RequiresGuest：表示当前Subject没有身份验证或通过记住我登录过，即是游客身份。</li>
<li>@RequiresRoles(value={“admin”, “user”}, logical= Logical.AND)：表示当前Subject 需要角色admin 和user</li>
<li>@RequiresPermissions(value={“user:a”, “user:b”}, logical= Logical.OR)：表示当前Subject 需要权限user:a或user:b。</li>
</ul>
<p>注解可以在 controller层、service层，但当service层有事务时就不要注解在servie层，不能让service是代理的代理。</p>
<h2 id="从数据表中查出资源权限">从数据表中查出资源权限</h2>
<p>目的：不用xml配置方式</p>
<pre><code class="language-xml">    &lt;bean id=&quot;shiroFilter&quot; class=&quot;org.apache.shiro.spring.web.ShiroFilterFactoryBean&quot;&gt;
        &lt;property name=&quot;securityManager&quot; ref=&quot;securityManager&quot;/&gt;
        &lt;property name=&quot;loginUrl&quot; value=&quot;/login.jsp&quot;/&gt;
        &lt;property name=&quot;successUrl&quot; value=&quot;/list.jsp&quot;/&gt;
        &lt;property name=&quot;unauthorizedUrl&quot; value=&quot;/unauthorized.jsp&quot;/&gt;
        &lt;property name=&quot;filterChainDefinitionMap&quot; ref=&quot;filterChainDefinitionMap&quot;&gt;&lt;/property&gt;
        &lt;!--
        &lt;property name=&quot;filterChainDefinitions&quot;&gt;
            &lt;value&gt;
                /login.jsp = anon
                /shiro/login = anon
                /shiro/logout = logout

                /user.jsp = roles[user]
                /admin.jsp = roles[admin]

                # everything else requires authentication:
                /** = authc
            &lt;/value&gt;
        &lt;/property&gt;
        --&gt;
    &lt;/bean&gt;

    &lt;!-- 配置一个 bean, 该 bean 实际上是一个 Map. 通过实例工厂方法的方式 --&gt;
    &lt;bean id=&quot;filterChainDefinitionMap&quot;
          factory-bean=&quot;filterChainDefinitionMapBuilder&quot; factory-method=&quot;buildFilterChainDefinitionMap&quot;&gt;&lt;/bean&gt;

    &lt;bean id=&quot;filterChainDefinitionMapBuilder&quot;
          class=&quot;com.demo.shiro.factory.FilterChainDefinitionMapBuilder&quot;&gt;&lt;/bean&gt;
</code></pre>
<pre><code class="language-java">/**
 * 从数据表中查出资源权限（这里是用静态模拟数据）
 *
 * 注意配置的顺序，因为有第一位置优先原则 —— 20190528
 */
public class FilterChainDefinitionMapBuilder {
    public LinkedHashMap&lt;String, String&gt; buildFilterChainDefinitionMap(){
        LinkedHashMap&lt;String, String&gt; map = new LinkedHashMap&lt;&gt;();

        map.put(&quot;/login.jsp&quot;, &quot;anon&quot;);
        map.put(&quot;/shiro/login&quot;, &quot;anon&quot;);
        map.put(&quot;/shiro/logout&quot;, &quot;logout&quot;);
        map.put(&quot;/user.jsp&quot;, &quot;authc,roles[user]&quot;);
        map.put(&quot;/admin.jsp&quot;, &quot;authc,roles[admin]&quot;);
        map.put(&quot;/list.jsp&quot;, &quot;user&quot;);

        map.put(&quot;/**&quot;, &quot;authc&quot;);

        return map;
    }
}
</code></pre>
<h2 id="在realm中跟数据库交互">在realm中跟数据库交互</h2>
<p>1）在配置realm时增加dao属性</p>
<pre><code class="language-xml">	&lt;!--自定义realm--&gt;
	&lt;bean id=&quot;userRealm&quot; class=&quot;cn.wolfcode.shiro.realm.UserRealm&quot;&gt;
		&lt;property name=&quot;userDAO&quot; ref=&quot;userDAOImpl&quot;/&gt;
	&lt;/bean&gt;
</code></pre>
<p>2）在自定义的realm中增加dao的set方法</p>
<pre><code class="language-java">public class UserRealm extends AuthorizingRealm {

    private IUserDAO userDAO;

    public void setUserDAO(IUserDAO userDAO) {
        this.userDAO = userDAO;
    }

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        return null;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        String username = (String) token.getPrincipal();

        //从数据库查用户信息
        User user = userDAO.getUserByUsername(username);

        if (user == null) {
            return null;
        }

        //第一个参数是 user，不是username ——20190529
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), getName());

        return info;
    }

    @Override
    public String getName() {
        return &quot;UserRealm&quot;;
    }
}
</code></pre>
<h2 id="静态授权">静态授权</h2>
<p>1）修改spring-shiro.xml配置文件</p>
<pre><code class="language-xml">	&lt;!-- 开启aop，对类代理 --&gt;
	&lt;aop:config proxy-target-class=&quot;true&quot;&gt;&lt;/aop:config&gt;
	&lt;!-- 开启shiro注解支持 --&gt;
	&lt;bean class=&quot;org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor&quot;&gt;
		&lt;property name=&quot;securityManager&quot; ref=&quot;securityManager&quot; /&gt;
	&lt;/bean&gt;
	&lt;!-- 定义需要特殊处理的异常，用类名或完全路径名作为key，异常页名作为值 --&gt;
	&lt;!--shiro权限异常处理--&gt;
	&lt;bean class=&quot;org.springframework.web.servlet.handler.SimpleMappingExceptionResolver&quot;&gt;
		&lt;property name=&quot;exceptionMappings&quot;&gt;
			&lt;props&gt;
				&lt;prop key=&quot;org.apache.shiro.authz.UnauthorizedException&quot;&gt;redirect:/nopermission.jsp&lt;/prop&gt;
			&lt;/props&gt;
		&lt;/property&gt;
	&lt;/bean&gt;
</code></pre>
<p>2）controller方法上添加权限注解</p>
<pre><code class="language-java">@Controller
@RequestMapping(&quot;/employee&quot;)
public class EmployeeController {
    @RequestMapping(&quot;&quot;)
    public String index() throws  Exception{
        System.out.println(&quot;执行了员工列表....&quot;);
        return &quot;employee&quot;;
    }
    @RequestMapping(&quot;/save&quot;)
    @RequiresPermissions(&quot;employee:save&quot;)
    public String save() throws  Exception{
        System.out.println(&quot;执行了员工保存....&quot;);
        return &quot;employee&quot;;
    }

    @RequestMapping(&quot;/edit&quot;)
    @RequiresPermissions(&quot;employee:edit&quot;)
    public String edit() throws  Exception{
        System.out.println(&quot;执行了员工编辑....&quot;);
        return &quot;employee&quot;;
    }

    @RequestMapping(&quot;/delete&quot;)
    @RequiresPermissions(&quot;employee:delete&quot;)
    public String delete() throws  Exception{
        System.out.println(&quot;执行了员工删除....&quot;);
        return &quot;employee&quot;;
    }
}
</code></pre>
<p>3）在自定义realm中添加权限</p>
<pre><code class="language-java">    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        User user = (User) principals.getPrimaryPrincipal();

        ArrayList&lt;String&gt; permissionList = new ArrayList&lt;&gt;();

        //静态授权
        if (&quot;zhangsan&quot;.equals(user.getUsername())) {
            permissionList.add(&quot;employee:edit&quot;);
        } else if (&quot;admin&quot;.equals(user.getUsername())) {
            permissionList.add(&quot;*:*&quot;);
        }

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(permissionList);

        return info;
    }
</code></pre>
<h2 id="资源授权动态">资源授权（动态）</h2>
<p>user/role/permission，它们之间的多对多关系</p>
<p><img src="https://gitee.com/rulerLwx/PicGo/raw/master/img/20200708090640.png" alt=""></p>
<p>对应的表</p>
<p><img src="https://gitee.com/rulerLwx/PicGo/raw/master/img/20200708090656.png" alt=""></p>
<h3 id="加载权限表达式到数据库">加载权限表达式到数据库</h3>
<p>1）在controller方法上添加两个标签：@RequiresPermissions、@PermissionName</p>
<pre><code class="language-java">    @RequestMapping(&quot;/save&quot;)
    @RequiresPermissions(&quot;employee:save&quot;)
    @PermissionName(&quot;员工保存&quot;)
    public String save() throws  Exception{
        System.out.println(&quot;执行了员工保存....&quot;);
        return &quot;employee&quot;;
    }
</code></pre>
<p>其中 @PermissionName 是自定义的注解</p>
<pre><code class="language-java">@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionName {
    String value();
}
</code></pre>
<p>2）添加加载权限表达式的controller</p>
<pre><code class="language-java">@Controller
public class PermissionController {

    //springmvc在启动时将所有标记有@RequestMapping的方法收集起来并封装到此对象
    @Autowired
    private RequestMappingHandlerMapping rmhm;

    @Autowired
    private IPermissionDAO permissionDAO;

    //将系统中所有的权限添加到数据库中
    @RequestMapping(&quot;/reload&quot;)
    public String reload() throws  Exception{
        //0、从数据库中查出所有权限表达式，然后对比，如果已经存在则跳过，如果不存在则添加
        List&lt;String&gt; resourcesList = permissionDAO.getAllResources();

        //1、获取所有标记有@RequestMapping的方法
        Map&lt;RequestMappingInfo, HandlerMethod&gt; handlerMethods = rmhm.getHandlerMethods();
        Collection&lt;HandlerMethod&gt; methods = handlerMethods.values();
        //2、遍历所有方法，判断是否有@RequiresPermissions标签
        for (HandlerMethod method : methods) {
            RequiresPermissions annotation = method.getMethodAnnotation(RequiresPermissions.class);
            if (annotation != null) {
                String permission = annotation.value()[0];//假设只有一个权限表达式

                //如果权限表达式已经存在，则跳过（不重复添加）
                if (resourcesList.contains(permission)) {
                    continue;
                }

                //3、如果有，解析权限表达式，封装成permission对象保存到permission表中
                Permission p = new Permission();
                p.setResource(permission);
                p.setName(method.getMethodAnnotation(PermissionName.class).value());

                //保存到数据表中
                permissionDAO.save(p);
            }
        }
        return &quot;main&quot;;
    }
}
</code></pre>
<p>看点：RequestMappingHandlerMapping、IPermissionDAO</p>
<p>3）请求 /reload 方法就可以动态加载权限表达式到数据库permission表中</p>
<p><img src="https://gitee.com/rulerLwx/PicGo/raw/master/img/20200708090727.png" alt=""></p>
<p>参考自己的代码：https://github.com/wolf-lea/shiro-v1.4.1-demo/tree/master/shiro-wolfcode-mvc</p>
<h3 id="角色权限">角色权限</h3>
<p>上面只是做了资源权限，跟角色还没有搭上关系，需要手动添加关系，或开发相关的功能来完善。</p>
<h2 id="动态授权数据库">动态授权——数据库</h2>
<pre><code class="language-java">public class UserRealm extends AuthorizingRealm {
    @Setter
    private IUserDAO userDAO;
    @Setter
    private IRoleDAO roleDAO;
    @Setter
    private IPermissionDAO permissionDAO;

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        User user = (User) principals.getPrimaryPrincipal();

        List&lt;String&gt; permissionList = new ArrayList&lt;&gt;();
        List&lt;String&gt; roleList = new ArrayList&lt;&gt;();

        //静态授权
//        if (&quot;zhangsan&quot;.equals(user.getUsername())) {
//            permissionList.add(&quot;employee:edit&quot;);
//        } else if (&quot;admin&quot;.equals(user.getUsername())) {
//            permissionList.add(&quot;*:*&quot;);
//        }

        //动态授权
        if (&quot;admin&quot;.equals(user.getUsername)) {
            //拥有所有权限
            permissionList.add(&quot;*:*&quot;);
            //拥有所有角色
            roleList = roleDAO.getAllRoleSn();
        } else {
            //根据用户id查询角色
            roleList = roleDAO.getRoleSnByUserId(user.getId());
            //根据用户id查询资源权限
            permissionList = permissionDAO.getPermissionResourceByUserId(user.getId());
        }

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addRoles(roleList);
        info.addStringPermissions(permissionList);
        return info;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        String username = (String) token.getPrincipal();

        //从数据库查用户信息
        User user = userDAO.getUserByUsername(username);

        if (user == null) {
            return null;
        }

        //第一个参数是 user，不是username ——20190529
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), getName());

        return info;
    }

    @Override
    public String getName() {
        return &quot;UserRealm&quot;;
    }
}
</code></pre>
<p>记得在spring-shiro.xml中注入依赖</p>
<pre><code class="language-xml">	&lt;!--自定义realm--&gt;
	&lt;bean id=&quot;userRealm&quot; class=&quot;cn.wolfcode.shiro.realm.UserRealm&quot;&gt;
		&lt;property name=&quot;userDAO&quot; ref=&quot;userDAOImpl&quot;/&gt;
		&lt;property name=&quot;roleDAO&quot; ref=&quot;roleDAOImpl&quot;/&gt;
		&lt;property name=&quot;permissionDAO&quot; ref=&quot;permissionDAOImpl&quot;/&gt;
	&lt;/bean&gt;
</code></pre>
<p>跟 静态授权 对比，角色和权限不是写死的，是从数据库是查出的</p>
<h2 id="对密码加密">对密码加密</h2>
<p>1）在 spring-shiro.xml 中添加 credentialsMatcher</p>
<pre><code class="language-xml">	&lt;!--密码加密算法--&gt;
	&lt;bean id=&quot;credentialsMatcher&quot;
		  class=&quot;org.apache.shiro.authc.credential.HashedCredentialsMatcher&quot;&gt;
		&lt;property name=&quot;hashAlgorithmName&quot; value=&quot;md5&quot; /&gt;
		&lt;property name=&quot;hashIterations&quot; value=&quot;3&quot; /&gt;
	&lt;/bean&gt;
</code></pre>
<p>记得在自定义的realm的bean中引入上面的credentialsMatcher</p>
<pre><code class="language-xml">	&lt;!--自定义realm--&gt;
	&lt;bean id=&quot;userRealm&quot; class=&quot;cn.wolfcode.shiro.realm.UserRealm&quot;&gt;
		&lt;property name=&quot;userDAO&quot; ref=&quot;userDAOImpl&quot;/&gt;
		&lt;property name=&quot;roleDAO&quot; ref=&quot;roleDAOImpl&quot;/&gt;
		&lt;property name=&quot;permissionDAO&quot; ref=&quot;permissionDAOImpl&quot;/&gt;
		&lt;property name=&quot;credentialsMatcher&quot; ref=&quot;credentialsMatcher&quot;/&gt;
	&lt;/bean&gt;
</code></pre>
<p>2）在认证方法中添加盐值（如果需要）</p>
<p>3）数据库中的密码是加密后的密文，可以使用 MD5Test 对密码事先修改为密文</p>
<h2 id="缓存管理">缓存管理</h2>
<p>1）依赖</p>
<pre><code class="language-xml">&lt;dependency&gt;
	&lt;groupId&gt;org.apache.shiro&lt;/groupId&gt;
	&lt;artifactId&gt;shiro-ehcache&lt;/artifactId&gt;
	&lt;version&gt;1.2.2&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
		&lt;groupId&gt;net.sf.ehcache&lt;/groupId&gt;
		&lt;artifactId&gt;ehcache-core&lt;/artifactId&gt;
		&lt;version&gt;2.6.8&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>2）修改spring-shiro.xml</p>
<p>添加配置：</p>
<pre><code class="language-xml">	&lt;!--如果导入的ehcache版本在2.5.0以上,需要配置如下.--&gt;
	&lt;!-- 缓存管理器开始 --&gt;
	&lt;bean id=&quot;cacheManager&quot; class=&quot;org.apache.shiro.cache.ehcache.EhCacheManager&quot;&gt;
		&lt;property name=&quot;cacheManager&quot; ref=&quot;ehCacheManager&quot;/&gt;
	&lt;/bean&gt;
	&lt;bean id=&quot;ehCacheManager&quot; class =&quot;org.springframework.cache.ehcache.EhCacheManagerFactoryBean&quot;&gt;
		&lt;property name=&quot;configLocation&quot; value=&quot;classpath:shiro-ehcache.xml&quot; /&gt;
		&lt;property name=&quot;shared&quot; value=&quot;true&quot;&gt;&lt;/property&gt;
	&lt;/bean&gt; 
</code></pre>
<p>在securityManager中添加cacheManager</p>
<pre><code>&lt;!-- 安全管理器 --&gt;
&lt;bean id=&quot;securityManager&quot; class=&quot;org.apache.shiro.web.mgt.DefaultWebSecurityManager&quot;&gt;
	&lt;property name=&quot;realm&quot; ref=&quot;myRealm&quot;&gt;&lt;/property&gt;
	&lt;property name=&quot;cacheManager&quot; ref=&quot;cacheManager&quot;&gt;&lt;/property&gt;
&lt;/bean&gt;
</code></pre>
<p>3）shiro-ehcache.xml配置文件</p>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;ehcache&gt;
    &lt;defaultCache
            maxElementsInMemory=&quot;1000&quot;
            eternal=&quot;false&quot;
            timeToIdleSeconds=&quot;120&quot;
            timeToLiveSeconds=&quot;120&quot;
            memoryStoreEvictionPolicy=&quot;LRU&quot;&gt;
    &lt;/defaultCache&gt;
&lt;/ehcache&gt;
</code></pre>
<p>清空缓存：</p>
<ul>
<li>如果用户正常退出，缓存自动清空。</li>
<li>如果用户非正常退出，缓存自动清空。</li>
<li>如果修改了用户的权限，而用户不退出系统，修改的权限无法立即生效。</li>
</ul>
<p>当用户权限修改后，用户再次登陆shiro会自动调用realm从数据库获取权限数据，如果在修改权限后想立即清除缓存则可以调用realm的clearCache方法清除缓存。</p>
<p>在realm中定义该方法:</p>
<pre><code class="language-java">//清除缓存
public void clearCached() {
     PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
     super.clearCache(principals);
}
</code></pre>
<p>在角色或权限service中,delete或者update方法去调用realm的清除缓存方法。注意：是手动调用。</p>
<h2 id="会话管理">会话管理</h2>
<p>Shiro提供了完整的企业级会话管理功能，不依赖于底层容器（如web容器tomcat），不管JavaSE还是JavaEE环境都可以使用，提供了会话管理、会话事件监听、会话存储/持久化、容器无关的集群、失效/过期支持、对Web 的透明支持、SSO 单点登录的支持等特性。</p>
<p>会话相关的API：</p>
<ul>
<li>Subject.getSession()：即可获取会话；其等价于Subject.getSession(true)，即如果当前没有创建Session 对象会创建一个；Subject.getSession(false)，如果当前没有创建Session 则返回null</li>
<li>session.getId()：获取当前会话的唯一标识</li>
<li>session.getHost()：获取当前Subject的主机地址</li>
<li>session.getTimeout() &amp; session.setTimeout(毫秒)：获取/设置当前Session的过期时间</li>
<li>session.getStartTimestamp() &amp; session.getLastAccessTime()：获取会话的启动时间及最后访问时间；如果是JavaSE应用需要自己定期调用session.touch() 去更新最后访问时间；如果是Web 应用，每次进入ShiroFilter都会自动调用session.touch() 来更新最后访问时间。</li>
<li>session.touch() &amp; session.stop()：更新会话最后访问时间及销毁会话；当Subject.logout()时会自动调用stop 方法来销毁会话。如果在web中，调用HttpSession. invalidate() 也会自动调用ShiroSession.stop方法进行销毁Shiro的会话</li>
<li>session.setAttribute(key, val) &amp; session.getAttribute(key) &amp; session.removeAttribute(key)：设置/获取/删除会话属性；在整个会话范围内都可以对这些属性进行操作</li>
</ul>
<p>Shiro提供的session可以访问到web中session域的数据：</p>
<pre><code class="language-java">public class ShiroService {
    @RequiresRoles({&quot;admin&quot;})
    public void testMethod(){
        System.out.println(&quot;testMethod, time: &quot; + new Date());

        Session session = SecurityUtils.getSubject().getSession();
        Object val = session.getAttribute(&quot;key&quot;);

        System.out.println(&quot;Service SessionVal: &quot; + val);
    }
}
</code></pre>
<p>也就是可以在service层访问session域数据，开发时可能用到。</p>
<h1 id="fqa">FQA</h1>
<h2 id="shiro内置realm之jdbcrealm">Shiro内置Realm之JdbcRealm</h2>
<p><a href="https://www.jianshu.com/p/c2ca70edfae6">https://www.jianshu.com/p/c2ca70edfae6</a></p>
<h2 id="shiro缓存使用redisehcache自带的mpcache实现的三种方式实例">Shiro缓存使用Redis、Ehcache、自带的MpCache实现的三种方式实例</h2>
<p><a href="https://www.cnblogs.com/zfding/p/8536480.html">https://www.cnblogs.com/zfding/p/8536480.html</a></p>

    </div>
    <div class="article-footer">
<blockquote class="mt-2x">
  <ul class="post-copyright list-unstyled">
    <li class="post-copyright-link hidden-xs">
      <strong>Permalink: </strong>
      <a href="https://rulerLwx.gitee.io/2020/07/java-framework-shiro/" title="Java-framework-Shiro" target="_blank" rel="external">https://rulerLwx.gitee.io/2020/07/java-framework-shiro/</a>
    </li>
    <li class="post-copyright-license">
      <strong>License：</strong><a href="http://creativecommons.org/licenses/by/4.0/deed.zh" target="_blank" rel="external">CC BY 4.0 CN</a>
    </li>
  </ul>
</blockquote>

<div class="panel panel-default panel-badger">
  <div class="panel-body">
    <figure class="media">
      <div class="media-left">
        <a href="https://gitee.com/rulerLwx" target="_blank" class="img-burn thumb-sm visible-lg">
          <img src="https://rulerLwx.gitee.io/avatar.png" class="img-rounded w-full" alt="">
        </a>
      </div>
      <div class="media-body">
        <h3 class="media-heading"><a href="https://gitee.com/rulerLwx" target="_blank"><span class="text-dark">rulerLwx</span><small class="ml-1x">thinking...</small></a></h3>
        <div>Good Good Study, Day Day Up~</div>
      </div>
    </figure>
  </div>
</div>
    </div>
  </article>
<section id="comments">
    <div id="vcomments"></div>
</section>

</div><nav class="bar bar-footer clearfix" data-stick-bottom>
    <div class="bar-inner">
        <ul class="pager pull-left">
            <li class="prev">
                <a href="https://rulerLwx.gitee.io/2020/07/java-framework-spring-1-%E5%AE%B9%E5%99%A8/" title="Java-framework-Spring-1-容器"><i
                        class="icon icon-angle-left"
                        aria-hidden="true"></i><span>&nbsp;&nbsp;Older</span></a>
            </li>
            <li class="next">
                <a href="https://rulerLwx.gitee.io/2020/07/java-framework-rabbitmq-2/"
                    title="Java-framework-RabbitMQ-2"><span>Newer&nbsp;&nbsp;</span><i
                        class="icon icon-angle-right" aria-hidden="true"></i></a>
            </li>
            
            <li class="toggle-toc">
                <a class="toggle-btn collapsed" data-toggle="collapse" href="#collapseToc" aria-expanded="false"
                    title="Catalogue" role="button">
                    <span>[&nbsp;</span><span>Catalogue</span>
                    <i class="text-collapsed icon icon-anchor"></i>
                    <i class="text-in icon icon-close"></i>
                    <span>]</span>
                </a>
            </li>
        </ul>
        <div class="bar-right">
            <div class="share-component" data-sites="weibo,qq,wechat"
                data-mobile-sites="weibo,qq,qzone"></div>
        </div>
    </div>
</nav>

</main><footer class="footer" itemscope itemtype="http://schema.org/WPFooter">
<ul class="social-links">
    <li><a href="https://gitee.com/rulerLwx" target="_blank" title="gitee" data-toggle=tooltip data-placement=top >
            <i class="icon icon-gitee"></i></a></li>
    <li><a href="https://github.com/wolf-lea" target="_blank" title="github" data-toggle=tooltip data-placement=top >
            <i class="icon icon-github"></i></a></li>
</ul>
  <div class="copyright">
    &copy;2020  -
    2020
    <div class="publishby">
        Theme by <a href="https://github.com/xiaoheiAh" target="_blank"> xiaoheiAh </a>base on<a href="https://github.com/xiaoheiAh/hugo-theme-pure" target="_blank"> pure</a>.
    </div>
  </div>
</footer>

<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.js?config=TeX-MML-AM_SVG"></script>
<script type="text/x-mathjax-config">
    MathJax.Hub.Config({
            showMathMenu: false, //disables context menu
            tex2jax: {
            inlineMath: [ ['$','$'], ['\\(','\\)'] ]
           }
    });
</script>


<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>
<script>
    window.jQuery || document.write('<script src="js/jquery.min.js"><\/script>')
</script>
<script type="text/javascript" src="https://cdn.staticfile.org/highlight.js/9.15.10/highlight.min.js"></script>
<script type="text/javascript" src="https://cdn.staticfile.org/highlight.js/9.15.10/languages/python.min.js" defer></script>
<script type="text/javascript" src="https://cdn.staticfile.org/highlight.js/9.15.10/languages/javascript.min.js" defer></script><script>
    hljs.configure({
        tabReplace: '    ', 
        classPrefix: ''     
        
    })
    hljs.initHighlightingOnLoad();
</script>
<script src="https://rulerLwx.gitee.io/js/application.min.bdeb64b910570b6c41badc6a05b7afb0c8ad9efd8525de3c7257d59e786326a3.js"></script>
<script src="https://rulerLwx.gitee.io/js/plugin.min.51ff8c7317566f82259170fa36e09c4493adc9b9378b427a01ad3f017ebac7dd.js"></script>

<script>
    (function (window) {
        var INSIGHT_CONFIG = {
            TRANSLATION: {
                POSTS: 'Posts',
                PAGES: 'Pages',
                CATEGORIES: 'Categories',
                TAGS: 'Tags',
                UNTITLED: '(Untitled)',
            },
            ROOT_URL: 'https:\/\/rulerLwx.gitee.io',
            CONTENT_URL: 'https:\/\/rulerLwx.gitee.io\/searchindex.json ',
        };
        window.INSIGHT_CONFIG = INSIGHT_CONFIG;
    })(window);
</script>
<script type="text/javascript" src="https://rulerLwx.gitee.io/js/insight.min.a343cd9a5a7698336b28ef3a7c16a3a1b1d2d5fb17dc8ed04022bbe08cc5459073a15bdafa3a8a58cdd56080784bdd69fa70b1ae8597565c799c57ed00f0e120.js" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.4.2/tocbot.min.js"></script>
<script>
    tocbot.init({
        
        tocSelector: '.js-toc',
        
        contentSelector: '.js-toc-content',
        
        headingSelector: 'h1, h2, h3',
        
        hasInnerContainers: true,
    });
</script>

<script src="https://cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/valine"></script>
<script type="text/javascript">
    var GUEST = ['nick', 'mail', 'link'];
    var meta = 'nick,mail';
    meta = meta.split(',').filter(function (item) {
        return GUEST.indexOf(item) > -1;
    });
    new Valine({
        el: '#vcomments',
        verify: null ,
        notify: null ,
        appId: 'IyAB0PSPRazTPDxitO1ddQ7O-gzGzoHsz',
        appKey: '5rBJTq4KidYF33eXwvRVhtEH',
        placeholder: 'enjoy~',
        avatar: 'mm',
        meta: meta,
        pageSize: '10' || 10,
        visitor: false 
});
</script>

  </body>
</html>
